package cn.lopr.mvc.frame.processors;

import cn.lopr.util.DateUtils;
import com.sun.org.apache.bcel.internal.generic.ClassGenException;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author shuangyang7
 * @date 2020/11/28 22:50
 */
public abstract class Processor {
    public static final Map<Class<?>, Object> primitiveDefaults = new HashMap<>();

    static {
        primitiveDefaults.put(Integer.TYPE, 0);
        primitiveDefaults.put(Short.TYPE, (short) 0);
        primitiveDefaults.put(Byte.TYPE, (byte) 0);
        primitiveDefaults.put(Float.TYPE, 0f);
        primitiveDefaults.put(Double.TYPE, 0d);
        primitiveDefaults.put(Long.TYPE, 0L);
        primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
        primitiveDefaults.put(Character.TYPE, (char) 0);
    }


    public Object processParam(String parameter, Object paramType) {
        if (parameter == null) {
            return null;
        }

        if (paramType.equals(String.class)) {
            return parameter;

        } else if (paramType.equals(Integer.TYPE) || paramType.equals(Integer.class)) {
            return Integer.valueOf(parameter);

        } else if (paramType.equals(Boolean.TYPE) || paramType.equals(Boolean.class)) {
            return Boolean.valueOf(parameter);

        } else if (paramType.equals(Long.TYPE) || paramType.equals(Long.class)) {
            return Long.valueOf(parameter);

        } else if (paramType.equals(Double.TYPE) || paramType.equals(Double.class)) {
            return Double.valueOf(parameter);

        } else if (paramType.equals(Float.TYPE) || paramType.equals(Float.class)) {
            return Float.valueOf(parameter);

        } else if (paramType.equals(Short.TYPE) || paramType.equals(Short.class)) {
            return Short.valueOf(parameter);

        } else if (paramType.equals(Byte.TYPE) || paramType.equals(Byte.class)) {
            return Byte.valueOf(parameter);

        } else if (paramType.getClass().getName().equals("java.util.Date") || paramType.equals(Date.class)) {
            return DateUtils.parseYMDHMS(parameter);
        }
        return parameter;
    }

    public boolean isCompatibleType(Object value, Class<?> type) {

        if (value == null) {
            return true;

        } else if (value.getClass().getName().equals("java.lang.String") && value instanceof String) {
            return true;

        } else if (value.getClass().getName().equals("java.lang.Integer") && value instanceof Integer) {
            return true;

        } else if (value.getClass().getName().equals("java.util.Date") && value instanceof Date) {
            return true;

        } else if (type.equals(Integer.TYPE) && value instanceof Integer) {
            return true;

        } else if (type.equals(Long.TYPE) && value instanceof Long) {
            return true;

        } else if (type.equals(Double.TYPE) && value instanceof Double) {
            return true;

        } else if (type.equals(Float.TYPE) && value instanceof Float) {
            return true;

        } else if (type.equals(Short.TYPE) && value instanceof Short) {
            return true;

        } else if (type.equals(Byte.TYPE) && value instanceof Byte) {
            return true;

        } else if (type.equals(Character.TYPE) && value instanceof Character) {
            return true;

        } else return type.equals(Boolean.TYPE) && value instanceof Boolean;
    }

    public void callSetter(Object bean, PropertyDescriptor prop, Object value) {
        Method setter = prop.getWriteMethod();
        if (setter == null) {
            return;
        }
        Class<?>[] params = setter.getParameterTypes();
        try {
            if (value instanceof java.util.Date) {
                final String targetType = params[0].getName();
                if ("java.sql.Date".equals(targetType)) {
                    value = new java.sql.Date(((java.util.Date) value).getTime());
                } else if ("java.sql.Time".equals(targetType)) {
                    value = new java.sql.Time(((java.util.Date) value).getTime());
                } else if ("java.sql.Timestamp".equals(targetType)) {
                    Timestamp tsValue = (Timestamp) value;
                    int nanos = tsValue.getNanos();
                    value = new java.sql.Timestamp(tsValue.getTime());
                    ((Timestamp) value).setNanos(nanos);
                }
            } else if (value instanceof String && params[0].isEnum()) {
                value = Enum.valueOf(params[0].asSubclass(Enum.class), (String) value);
            }
            if (this.isCompatibleType(value, params[0])) {
                setter.invoke(bean, value);
            } else {
                throw new ClassGenException(
                        "Cannot set " + prop.getName() + ": incompatible types, cannot convert "
                                + value.getClass().getName() + " to " + params[0].getName());
            }
        } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
            throw new ClassGenException(
                    "Cannot set " + prop.getName() + ": " + e.getMessage());
        }
    }


    public Object processColumn(ResultSet rs, int index, Class<?> propType) throws SQLException {
        if (!propType.isPrimitive() && rs.getObject(index) == null) {
            return null;
        }

        if (propType.equals(String.class)) {
            return rs.getString(index);

        } else if (propType.equals(Integer.TYPE) || propType.equals(Integer.class)) {
            return rs.getInt(index);

        } else if (propType.equals(Boolean.TYPE) || propType.equals(Boolean.class)) {
            return rs.getBoolean(index);

        } else if (propType.equals(Long.TYPE) || propType.equals(Long.class)) {
            return rs.getLong(index);

        } else if (propType.equals(Double.TYPE) || propType.equals(Double.class)) {
            return rs.getDouble(index);

        } else if (propType.equals(Float.TYPE) || propType.equals(Float.class)) {
            return rs.getFloat(index);

        } else if (propType.equals(Short.TYPE) || propType.equals(Short.class)) {
            return rs.getShort(index);

        } else if (propType.equals(Byte.TYPE) || propType.equals(Byte.class)) {
            return rs.getByte(index);

        } else if (propType.equals(Timestamp.class)) {
            return rs.getTimestamp(index);

        } else if (propType.equals(SQLXML.class)) {
            return rs.getSQLXML(index);

        } else {
            return rs.getObject(index);
        }
    }

    public PropertyDescriptor[] propertyDescriptors(Class<?> type) throws SQLException {
        BeanInfo beanInfo = null;
        try {
            beanInfo = Introspector.getBeanInfo(type);

        } catch (IntrospectionException e) {
            e.printStackTrace();
            throw new SQLException("Bean introspection failed: " + e.getMessage());
        }
        return beanInfo.getPropertyDescriptors();
    }

    public <T> T newInstance(Class<T> c) {
        try {
            return c.newInstance();

        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            throw new ClassGenException("Cannot create " + c.getName() + ": " + e.getMessage());
        }
    }

}
